namespace Angelus.Oracle.Entity.Common.Entity
{
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Diagnostics.Contracts;
    using System.Globalization;

    /// <summary>
    /// Description of OracleBuilder.
    /// </summary>
    internal class SqlSelectStatement : ISqlFragment
    {
        #region Fields

        private readonly SqlBuilder _from;
        private readonly SqlBuilder _select;
        private List<string> _aliases;
        private Dictionary<Symbol, bool> _extents;
        private List<Symbol> _fromSymbols;
        private SqlBuilder _groupBy;
        private SqlBuilder _orderBy;
        private SqlBuilder _where;

        #endregion

        #region Constructors

        public SqlSelectStatement()
        {
            this._select = new SqlBuilder();
            this._from = new SqlBuilder();
        }

        #endregion

        #region Properties

        /// <summary>
        /// 
        /// </summary>
        internal List<string> Aliases
        {
            get
            {
                if (null == this._aliases)
                {
                    this._aliases = new List<string>();
                }
                return this._aliases;
            }
        }

        /// <summary>
        /// Do we need to add a DISTINCT at the beginning of the SELECT
        /// </summary>
        internal bool IsDistinct { get; set; }

        /// <summary>
        /// 
        /// </summary>
        internal List<Symbol> AllJoinExtents { get; set; }

        /// <summary>
        /// 
        /// </summary>
        internal List<Symbol> FromExtents
        {
            get
            {
                if (null == this._fromSymbols)
                {
                    this._fromSymbols = new List<Symbol>();
                }

                return this._fromSymbols;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        internal Dictionary<Symbol, bool> OutExtents
        {
            get
            {
                if (null == this._extents)
                {
                    this._extents = new Dictionary<Symbol, bool>();
                }

                return this._extents;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        internal SqlBuilder Select
        {
            get { return this._select; }
        }

        /// <summary>
        /// 
        /// </summary>
        internal SqlBuilder From
        {
            get { return this._from; }
        }

        /// <summary>
        /// 
        /// </summary>
        internal SqlBuilder Where
        {
            get
            {
                if (null == this._where)
                {
                    this._where = new SqlBuilder();
                }

                return this._where;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        internal SqlBuilder GroupBy
        {
            get
            {
                if (null == this._groupBy)
                {
                    this._groupBy = new SqlBuilder();
                }

                return this._groupBy;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        internal SqlBuilder OrderBy
        {
            get
            {
                if (null == this._orderBy)
                {
                    this._orderBy = new SqlBuilder();
                }

                return this._orderBy;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        internal static SqlBuilder Top
        {
            get { return null; }
        }

        /// <summary>
        /// Indicates whether it is the top most select statement,
        /// if not Order By should be omitted unless there is a corresponding TOP
        /// </summary>
        internal bool IsTopMost { get; set; }

        #endregion

        #region ISqlFragment Implementation

        /// <summary>
        /// Write out a SQL select statement as a string.
        /// </summary>
        /// <param name="writer"></param>
        /// <param name="generator"></param>
        public void WriteSql(SqlWriter writer, SqlGenerator generator)
        {
            if ((null != this._extents) && (0 < this._extents.Count))
            {
                foreach (Symbol extent in this._extents.Keys)
                {
                    JoinSymbol joinSymbol = extent as JoinSymbol;
                    if (null != joinSymbol)
                    {
                        foreach (Symbol symbol in joinSymbol.FlattenedExtentList)
                        {
                            this.Aliases.Add(symbol.NewName);
                        }
                    }
                    else
                    {
                        this.Aliases.Add(extent.NewName);
                    }
                }
            }

            // And then rename each of the FromExtents we have
            // If AllJoinExtents is non-null - it has precedence.
            // The new name is derived from the old name - we append an increasing int.
            List<Symbol> extents = this.AllJoinExtents ?? this.FromExtents;
            foreach (Symbol alias in extents)
            {
                if (this.Aliases.Contains(alias.Name))
                {
                    int sequence = generator.AllExtentNames[alias.Name];
                    string newName;
                    do
                    {
                        ++sequence;
                        newName = alias.Name + sequence.ToString(CultureInfo.InvariantCulture);
                    } while (generator.AllExtentNames.ContainsKey(newName));

                    generator.AllExtentNames[alias.Name] = sequence;
                    alias.NewName = newName;

                    // Add extent to list of known names (although sequence is always incrementing, "prefix11" can
                    // eventually collide with "prefix1" when it is extented)
                    generator.AllExtentNames[newName] = 0;
                }

                // Add the current alias to the list, so that the extents
                // that follow do not collide with me.
                this.Aliases.Add(alias.NewName);
            }

            // Increase the indent, so that the Sql statement is nested by one tab.
            writer.Indent++;

            writer.Write("SELECT ");
            if (this.IsDistinct)
            {
                writer.Write("DISTINCT ");
            }

            if ((null == this._select) || this.Select.IsEmpty)
            {
                Contract.Assert(false, "we have removed all possibilities of SELECT *.");
                writer.Write("*");
            }
            else
            {
                this.Select.WriteSql(writer, generator);
            }

            writer.WriteLine();
            writer.Write("FROM ");
            this.From.WriteSql(writer, generator);

            if ((null != this._where) && !this.Where.IsEmpty)
            {
                writer.WriteLine();
                writer.Write("WHERE ");
                this.Where.WriteSql(writer, generator);
            }

            if ((null != this._groupBy) && !this.GroupBy.IsEmpty)
            {
                writer.WriteLine();
                writer.Write("GROUP BY ");
                this.GroupBy.WriteSql(writer, generator);
            }

            if ((null != this._orderBy) && !this.OrderBy.IsEmpty && (this.IsTopMost || (null != Top)))
            {
                writer.WriteLine();
                writer.Write("ORDER BY ");
                this.OrderBy.WriteSql(writer, generator);
            }

            --writer.Indent;
        }

        #endregion
    }
}